package com.pmstation.skin

import java.awt.Color
import java.awt.image.BufferedImage
import com.pmstation.skin._
import collection.mutable

/**
 * Treats colors and brightness independent. Increases r-g-b components until one of them is 255 -
 * so converts color to max brightness, then analyzes in 2D.
 * Colors with r<g or r<b are definitely NOT skin according to histograms (paintHisto),
 * so only 1 of 3 squares of color space remains to analyze
 */

class MaxBrightSkinFilter(skinSrc: PixelsSrc, noSkinSrc: PixelsSrc){
  //val minBright=60

  val rmax = Array.ofDim[Int](256,256)
  val gmax = Array.ofDim[Int](256,256)
  val bmax = Array.ofDim[Int](256,256)
  val skinBrightness = new Array[Int](255*3)

  val bcount=256
  val blocks = Array.ofDim[Int](bcount, bcount) // red-max; (g,b) are indexes

  val blocksNoSkin = Array.ofDim[Int](bcount, bcount) // red-max; (g,b) are indexes


  var countSrcSkin, countSrcNonSkin=0
  for(rgb <- skinSrc){
    countSrcSkin += 1
    val c = new Color(rgb)
    update(c.getRed, c.getGreen, c.getBlue)
  }

  for(rgb <- noSkinSrc){
    countSrcNonSkin += 1
    val c = new Color(rgb)
    updateNoSkin(c.getRed, c.getGreen, c.getBlue)
  }

  var sumSkin = blocks.flatten.sum
  val sumNoSkin = blocksNoSkin.flatten.sum

  var blockThreshold = percentileCalc()

  println("MaxBrightSkinFilter srcSkin=%s srcNonskin=%s"
          .format(countSrcSkin, countSrcNonSkin))

  def percentileCalc() = {
    val blockCellCounts = new mutable.HashMap[Int,Int]
    for{ i<-0 until bcount
         j<-0 until bcount
         c = blocks(i)(j)
         if c>0
    }{
        blockCellCounts(c) = blockCellCounts.getOrElse(c, 0) + 1
    }
    val sorted = blockCellCounts.toArray.sortBy(_._1)
    //val ignoreCount = sumSkin * 0.05 // ignoring 5% of blocks, 95% of skin remains
    val ignoreCount = sumSkin * 0.20 // ignoring 10% of blocks, 90% of skin remains
    var sumIgnore = 0
    var cmax=0
    for{
      orderedCount <- sorted
      c = orderedCount._1
      hits = orderedCount._2
    }{
      if( sumIgnore==0 || sumIgnore <= ignoreCount ){
        sumIgnore += c*hits
        cmax = c
      }
    }

    var finalSumIgnore=0
    for{ i<-0 until bcount
         j<-0 until bcount
         c = blocks(i)(j)
         if c<cmax
    }{
      finalSumIgnore += c
    }


    println("threshold %s sumignore=%s final-sumignore %s b20=%s b21=%s".
            format(cmax, sumIgnore, finalSumIgnore, blockCellCounts(20), blockCellCounts(21)))
    cmax
  }

  case class Weights(skinCount:Int, skin:Double, nonskin: Double)

  def isSkin(rgb: Int):Boolean={
    val w = testSkin(rgb)
    //w!=null && w.skin > w.nonskin
    //w!=null && w.skin > (1.0 / 256 / 256)
    //w!=null && w.skinCount>1
    w!=null && w.skinCount>=blockThreshold
  }

  def testSkin(rgb: Int) : Weights = {
    if( EasyNotSkin.easyNotSkin(rgb) ){
      return null
    }

    val R = ((rgb >> 16) & 0xFF)
    val G = ((rgb >> 8) & 0xFF)
    val B = ((rgb >> 0) & 0xFF)

    if( R>=G && R>=B ){
      val gplus = if(R==0) 0 else G*255/R
      val bplus = if(R==0) 0 else B*255/R

      val x = gplus
      val y = bplus
      val countSkin = blocks(x)(y)
      val countNoSkin = blocksNoSkin(x)(y)

      val weightSkin = countSkin.toDouble / sumSkin
      val weightNoSkin = countNoSkin.toDouble / sumNoSkin
      //weightSkin > weightNoSkin
      Weights(countSkin, weightSkin, weightNoSkin)
    }else{
      null
    }
  }


  def paintHisto(refColor: Color) : BufferedImage = {
    val imgNew = new BufferedImage(256*3, 256*3, BufferedImage.TYPE_INT_RGB)

    val rc = colorToMaxRed(refColor)

    class MaxPainter(a:Array[Array[Int]], showColorF: (Int,Int) => Color){
      val m : Int = a.flatten.max
      def binColor(x:Int, y:Int):Int={
        if( a(x)(y) < blockThreshold ) 0 else Color.WHITE.getRGB
      }
      def showColor(x:Int, y:Int)={
        if( a(x)(y)==0 ) 0 else showColorF(x,y).getRGB
      }
      def scaleColor(x:Int, y:Int)={
        256 * a(x)(y)*255/m
      }
    }
    val rpainter = new MaxPainter(rmax, (x,y) => new Color(255,x,y)){
      override def binColor(g: Int, b: Int):Int = {
        if( (g-rc._1).abs + (b-rc._2).abs < 10 ){
          Color.GREEN.getRGB
        }else{
          super.binColor(g,b)
        }
      }
    }
    val gpainter = new MaxPainter(gmax, (x,y) => new Color(x,255,y))
    val bpainter = new MaxPainter(bmax, (x,y) => new Color(x,y,255))

    for(i<-0 to 255; j<-0 to 255){
      //imgNew.setRGB(i,j, rpainter.scaleColor(i,j) )
      imgNew.setRGB(i,j, if( isSkin(new Color(255,i,j).getRGB) ) Color.RED.getRGB else Color.GREEN.getRGB )
      imgNew.setRGB(i+256,j, gpainter.scaleColor(i,j) )
      imgNew.setRGB(i+256*2,j, bpainter.scaleColor(i,j) )

      imgNew.setRGB(i,j+256, rpainter.binColor(i,j) )
      imgNew.setRGB(i+256,j+256, gpainter.binColor(i,j) )
      imgNew.setRGB(i+256*2,j+256, bpainter.binColor(i,j) )

      imgNew.setRGB(i,j+2*256, rpainter.showColor(i,j) )
      imgNew.setRGB(i+256,j+2*256, gpainter.showColor(i,j) )
      imgNew.setRGB(i+256*2,j+2*256, bpainter.showColor(i,j) )
    }
    val g = imgNew.getGraphics
    g.setColor(Color.WHITE)
    g.drawLine(0,256,256*3,256)
    g.drawLine(0,256*2,256*3,256*2)

    g.drawLine(256,0,256,256*3)
    g.drawLine(256*3,0, 256*3,256*3)

    imgNew
  }

  def updateNoSkin(r:Int,g:Int,b:Int){
    if( r>=g && r>=b ){
      val gm:Int = if(r==0) 0 else g*255/r
      val bm:Int = if(r==0) 0 else b*255/r
      blocksNoSkin(gm )(bm ) += 1
    }
  }

  def colorToMaxRed(c:Color):(Int,Int) = {
    if( c.getRed>=c.getGreen && c.getRed>=c.getBlue ){
      val gm:Int = if(c.getRed==0) 0 else c.getGreen*255/c.getRed
      val bm:Int = if(c.getRed==0) 0 else c.getBlue*255/c.getRed
      (gm, bm)
    }else{
      throw new Exception(c.toString)
    }
  }

  def update(rgb:Int){
    val c = new Color(rgb)
    update(c.getRed, c.getGreen, c.getBlue)
  }

  def update(r:Int,g:Int,b:Int){
    skinBrightness(r+g+b) += 1
    if( r>=g && r>=b ){
      updateMax(r,rmax,g,b)

      val gm:Int = if(r==0) 0 else g*255/r
      val bm:Int = if(r==0) 0 else b*255/r
      blocks(gm )(bm ) += 1
    }else if( g>=r && g>=b ){
      updateMax(g,gmax,r,b)
    }else{
      updateMax(b,bmax,r,g)
    }
  }
  def updateMax(max:Int, a:Array[Array[Int]], x:Int, y:Int){
    val xm:Int = if(max==0) 0 else x*255/max
    val ym:Int = if(max==0) 0 else y*255/max
    a(xm)(ym) += 1
  }

  def learnProcess(img:BufferedImage):Int = {
    var newSkin=0
    for( i<-0 until img.getWidth; j<-0 until img.getHeight ){
      val c = img.getRGB(i,j)
      if( isSkin(c) ){
        update(c)
      }else{
        var closeSkins = 0
        for( di <- -1 to 1; dj <- -1 to 1){
          val pi = i+di
          val pj = j+dj
          if( pi>=0 && pj>=0 && pi<img.getWidth && pj<img.getHeight ){
            val pc = img.getRGB(pi, pj)
            if( ColorDiff.diff(c, pc)<5 && isSkin(pc) ){
              closeSkins += 1
            }
          }
        }
        if( closeSkins>1 ){
          update(c)
          newSkin += 1
        }
      }
    }

    sumSkin = blocks.flatten.sum
    blockThreshold = percentileCalc()
    println("learned new %s threshold %s all %s ".format(newSkin, blockThreshold, sumSkin))

    newSkin
  }
}
